package de.mmis.core.base.abstracttree.serializer.internal;

import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.util.HashMap;
import java.util.Map;

import de.mmis.core.base.DeserializableAnnotations.ConstructorDeserializable;
import de.mmis.core.base.DeserializableAnnotations.DeserializationConstructor;
import de.mmis.core.base.MethodDescriptor.P;
import de.mmis.core.base.abstracttree.InnerNode;
import de.mmis.core.base.abstracttree.LeafNode;
import de.mmis.core.base.abstracttree.Tree;
import de.mmis.core.base.abstracttree.serializer.DeserializableException;
import de.mmis.core.base.abstracttree.serializer.Serializer;
import de.mmis.core.base.abstracttree.serializer.SerializerHelper;
import de.mmis.core.base.abstracttree.serializer.selector.InstanceSelector;
import de.mmis.core.base.abstracttree.serializer.selector.SimpleSelector;
import de.mmis.core.base.gclass.GClass;

/**
 * Default Serializer class for (de)serializing general Objects from/to Trees
 * 
 * @author Christian Koch
 */
public class ConstructorDeserializableSerializer implements Serializer<Object> {

	// note that this method is intentionally not annotated with
	// DeserializationMethod
	public static InstanceSelector deserialize(Tree[] exps,
			ClassLoader classLoader, String id, Map<String, Object> idmap,
			Class<?> constructorDeserializableClass)
			throws DeserializableException {

		final Map<String, Tree> dataMap = new HashMap<String, Tree>();
		for (Tree exp : exps) {
			if (exp.getType() != de.mmis.core.base.abstracttree.Tree.Type.INNER
					|| ((InnerNode) exp).getNumberOfSubTrees() != 2)
				throw new DeserializableException(new InnerNode(exps), "item "
						+ exp + " is no 2-elem Struct");

			if (((InnerNode) exp).getSubTree(0).getType() != de.mmis.core.base.abstracttree.Tree.Type.LEAF)
				throw new DeserializableException(new InnerNode(exps),
						"map item " + exp + " has no atom key");

			dataMap.put(
					((LeafNode) ((InnerNode) exp).getSubTree(0)).getUTF8Data(),
					((InnerNode) exp).getSubTree(1));
		}

		Class<?> c = constructorDeserializableClass;
		if (c == null)
			throw new DeserializableException(new InnerNode(exps),
					"cannot handle arrays here");

		nextcons: for (Constructor<?> cons : c.getConstructors()) {
			if (cons.getAnnotation(DeserializationConstructor.class) == null)
				continue;

			Object[] params = new Object[cons.getParameterTypes().length];
			nextparam: for (int i = 0; i < params.length; i++) {
				for (int j = 0; j < cons.getParameterAnnotations()[i].length; j++) {
					if (!(cons.getParameterAnnotations()[i][j] instanceof P))
						continue;

					String key = "!"
							+ ((P) cons.getParameterAnnotations()[i][j])
									.value();
					if (!dataMap.containsKey(key))
						continue nextcons;

					params[i] = dataMap.get(key).getAs(
							GClass.fromClass(cons.getParameterTypes()[i]),
							classLoader, idmap);
					continue nextparam;
				}

				throw new DeserializableException(
						new InnerNode(exps),
						"constructor "
								+ cons
								+ " of class "
								+ c
								+ " is annotated with @DeserializationConstructor, but parameter "
								+ (i + 1) + " has no @P annotation as required");
			}
			try {
				return new SimpleSelector(cons.newInstance(params), id, idmap);
			} catch (IllegalArgumentException e) {
				throw new DeserializableException(new InnerNode(exps), e,
						"exception while invoking constructor " + cons);
			} catch (InstantiationException e) {
				throw new DeserializableException(new InnerNode(exps), e,
						"exception while invoking constructor " + cons);
			} catch (IllegalAccessException e) {
				throw new DeserializableException(new InnerNode(exps), e,
						"exception while invoking constructor " + cons);
			} catch (InvocationTargetException e) {
				throw new DeserializableException(new InnerNode(exps), e,
						"exception while invoking constructor " + cons);
			}
		}

		throw new DeserializableException(new InnerNode(exps),
				"no annotated constructor found which needs parameters found in the map");

	}

	@Override
	public Tree serialize(Object object, boolean explicit,
			Map<Object, String> refmap, ClassLoader classLoader) {

		InnerNode ret = new InnerNode();

		Class<?> c = object.getClass();

		ConstructorDeserializable a = c
				.getAnnotation(ConstructorDeserializable.class);
		if (a == null)
			throw new IllegalArgumentException("given object " + object
					+ " is not annotated with ConstructorDeserializable");

		if (explicit)
			ret.add(new LeafNode(SerializerHelper.typeIdentifierPrefix
					+ a.typeIdentifier()));

		ObjectSerializer.appendSerializedGetters(ret, object, explicit, refmap,
				classLoader);

		return ret;
	}
}
